import std.stdio: writefln;
import std.string: toStringz;
import derelict.sdl.sdl;

import std.math;
import maths.Vec;
import maths.Matrix;
import maths.Quat;

import Utils;
import Color;
import Point;
import Pixels;
import Lines;
import Circles;
import Triangles;
import Rectangle;
import Curves;
import Blur;
import FloodFill;
import ScanLine;

point[] pkty;
Curve[] curves;
FloodFill[] ff;
ScanLine sl;
Blur gb;
uint b = 0;
float sc = 1.f;
float tx=0, ty=0;

SDL_Surface *screen;
SDL_Surface *bitmap;
bool lmb		= false;
bool rmb		= false;
bool blur		= false;
bool drawLines	= true;
bool drawPoints	= true;
bool scanLine	= false;
bool interpolacja = false;
point *activePoint;

float foo = 1.;

void init() {
	DerelictSDL.load();

	// initialize SDL's VIDEO module
	SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	screen = SDL_SetVideoMode(xResolution, yResolution, bitsPerPixel, SDL_SWSURFACE );
	SDL_WM_SetCaption(toStringz("LGiM"), null);
	gb = new Blur();
	sl = new ScanLine(screen, 0xFFFFFF);
}


// be nice and release all resources
void cleanup() {
	SDL_Quit();
	DerelictSDL.unload();
}

point* getNearestPoint(Curve[] cur, point p, int maxDist){
	point* pkt;
	foreach(c; cur){
		pkt = c.getNearestPoint(p, maxDist);
		if(pkt !is null)
			return pkt;
	}
	return null;
}

void keyDown(SDL_KeyboardEvent e){
	if(e.keysym.sym == SDLK_ESCAPE) throw new Exception("KungFuDeathGrip");
	if(e.keysym.sym == SDLK_b) blur = !blur;
	if(e.keysym.sym == SDLK_w) b-=5;
	if(e.keysym.sym == SDLK_e) b+=5;
	if(b<=0) b += 360;
	if(e.keysym.sym == SDLK_o) sc -= .05;
	if(e.keysym.sym == SDLK_p) sc += .05;
	if(sc<=0) sc += .05;
	if(e.keysym.sym == SDLK_i) interpolacja = !interpolacja;
	if(e.keysym.sym == SDLK_UP) ty-=5;
	if(e.keysym.sym == SDLK_DOWN) ty+=5;
	if(e.keysym.sym == SDLK_LEFT) tx-=5;
	if(e.keysym.sym == SDLK_RIGHT) tx+=5;
	if(e.keysym.sym == SDLK_q){
		pkty.length = 0;
		curves.length = 0;
		ff.length = 0;
	}
	if(e.keysym.sym == SDLK_PERIOD) drawPoints = !drawPoints;
	if(e.keysym.sym == SDLK_SLASH) drawLines = !drawLines;
	if(e.keysym.sym == SDLK_s) scanLine = !scanLine;
	if(e.keysym.sym == SDLK_z){
		curves ~= new BezierCurve(pkty, /*0x0004D2*/color(0u, 4u, 0xD2u).ui);
		pkty.length = 0;
	}
	if(e.keysym.sym == SDLK_x){
		curves ~= new BSplineCurve(pkty, /*0x0004D2*/color(0u, 4u, 0xD2u).ui);
		pkty.length = 0;
	}
	if(e.keysym.sym == SDLK_c){
		curves ~= new NURBSCurve(pkty, /*0x0004D2*/color(0u, 4u, 0xD2u).ui);
		pkty.length = 0;
	}
}

void mouseUp(SDL_MouseButtonEvent e){
	if(e.button == SDL_BUTTON_LEFT){
		lmb = false;
		activePoint = null;
	}
}

void mouseDown(SDL_MouseButtonEvent e){
	if(e.button == SDL_BUTTON_LEFT){
		lmb = true;
		activePoint = getNearestPoint(curves, point(e.x, e.y), 4);
		if(activePoint is null){
			pkty ~= point(e.x, e.y);
//			pkty.length = pkty.length + 1;
		}
	}
	if(e.button == SDL_BUTTON_RIGHT){
		writefln(e.x," ", e.y);
		ff ~= new FloodFill(point(e.x, e.y));
	}
}

void mouseMotion(SDL_MouseMotionEvent e){
	if(lmb && activePoint !is null){
		activePoint.x = e.x;
		activePoint.y = e.y;
	}
}

void transform(SDL_Surface *surface, SDL_Surface *bitmap, vec3 translate, float alpha, float scale){
	mat4 matrix = mat4.translation(scale*vec3(bitmap.w/2,bitmap.h/2,0.)) * mat4.translation(vec3(tx,ty,0.)) * mat4.eulerRotation(0, 0, alpha) * mat4.translation(-scale*vec3(bitmap.w/2,bitmap.h/2,0.))* mat4.scaling(vec3(scale,scale,1));
	mat4 invert = matrix.inverse();
	
	vec3[] newImage;
	newImage ~= vec3(0.,0.,0.);
	newImage ~= vec3(bitmap.w,0.,0.);
	newImage ~= vec3(bitmap.w, bitmap.h, 0.);
	newImage ~= vec3(0., bitmap.h, 0.);
	foreach(inout vec; newImage)
		vec = matrix.xform(vec);
	rectangle(screen, newImage, 1234);
	point inside = point(cast(int)((newImage[0].x+newImage[2].x)/2), cast(int)((newImage[0].y+newImage[2].y)/2));
	FloodFill[] ff1;
	ff1 ~= new FloodFill(inside);
	ff1[0].fill(surface, bitmap, inside, invert, interpolacja);
	ff1.length = 0;
}


void drawFrame(){
	/+	mat4 matrix;
		matrix = mat4.translation(vec3(bitmap.w/2,bitmap.h/2,0.)) * mat4.translation(vec3(tx,ty,0.)) * mat4.eulerRotation(0, 0, b) * mat4.translation(-vec3(bitmap.w/2,bitmap.h/2,0.)) ;
		
 	for(int i = 0; i < bitmap.w; i++)
 		for(int j = 0; j < bitmap.h; j++)
 			putPixel(screen, matrix.xform(vec3(i,j,0)), getPixel(bitmap, i, j));
+/
	transform(screen, bitmap, vec3(tx, ty, 0), b, sc);

	foreach(curve; curves)
		curve.draw(screen, drawLines, drawPoints);

	foreach(pkt; pkty){
		putPixel(screen, pkt.x, pkt.y, 0xFF0000);
		circle(screen, pkt.x, pkt.y, 2, 0xFF0000);
	}
	foreach(f; ff)
		f.fill(screen);
	if(scanLine){ sl.scan(screen);scanLine = false;}
	sl.draw(screen);
	if(blur) gb.blur(screen);
/+ 	for(uint i=0; i < 256; i++)
		for(uint j=0; j< 256; j++){
			putPixel(screen, i, j, color(i,j,b*2).ui);
			putPixel(screen, i+260, j, color(i,b*2,j).ui);
			putPixel(screen, i+520, j, color(b*2,i,j).ui);
		}

	for(uint i=0; i < 101; i++)
		for(uint j=0; j< 101; j++){
			putPixel(screen, i, j+300, color.hsvToRgb((i/100.f)*360,j/100.f,b/100.f).ui);
			putPixel(screen, i+150, j+300, color.hsvToRgb((i/100.f)*360f,b/100.f,j/100.f).ui);
			putPixel(screen, i+300, j+300, color.hsvToRgb((b/100.f)*360.f,i/100.f,j/100.f).ui);
		} +/
}

void main() {
	uint startTicks;
	long frames;
	

/+	pkty ~= point(200, 300);
	pkty ~= point(50, 80);
	pkty ~= point(150, 50);
	pkty ~= point(300, 100);
	pkty ~= point(300, 300);
	pkty ~= point(200, 400);
	pkty ~= point(150, 200);
	pkty.length = 5;
	color foo = color(1u,2u,3u);
	bc = new BezierCurve(pkty, /*0x0004D2*/color(0u, 4u, 0xD2u).ui);
	bs = new BSplineCurve(pkty, 0x0004D2);
	curves ~= bc;
	curves ~= bs;
	curves.length = 2;
	pkty.length = 0;+/
	init();
	bitmap = SDL_LoadBMP("image.bmp");
	
	if(bitmap is null)
		writefln("blah");
		
	
	writefln("Must lock: ",SDL_MUSTLOCK(screen));
	scope(exit){
		cleanup();	  // when we exit, perform cleanup
	}

	startTicks = SDL_GetTicks();
	mainLoop:
	while (true) {
		SDL_Event event;
		{
			uint ticks = SDL_GetTicks();
			if(ticks - startTicks > fpsRefresh){
				writefln("FPS:",1000.f * cast(float)frames/2000.f);
				writefln(b);
				startTicks = ticks;
				frames = 0;
			}
		}
		frames++;
		// handle all SDL events that we might've received in this loop iteration
		while (SDL_PollEvent(&event)) {
			switch (event.type) {
				// user has clicked on the window's close button
				case SDL_QUIT:
					break mainLoop;
				case SDL_KEYDOWN:
					keyDown(event.key);
					break;
				case SDL_MOUSEBUTTONDOWN:
					mouseDown(event.button);
					break;
				case SDL_MOUSEBUTTONUP:
					mouseUp(event.button);
					break;
				case SDL_MOUSEMOTION:
					mouseMotion(event.motion);
					break;
				// by default, we do nothing => break from the switch
				default:
					break;
			}
		}
	
		SDL_LockSurface(screen);
		clearScreen(screen);
		drawFrame();
		SDL_UnlockSurface(screen);
		SDL_UpdateRect(screen,0,0,0,0);
//		SDL_Flip(screen);
	}
}
